;;; Copyright 2014 The Go Authors. All rights reserved.
;;; Use of this source code is governed by a BSD-style
;;; license that can be found in the LICENSE file.
;;;
;;; Integration of the 'gorename' tool into Emacs.
;;;
;;; To install:
;;; % go get golang.org/x/tools/cmd/gorename
;;; % go build golang.org/x/tools/cmd/gorename
;;; % mv gorename $HOME/bin/         # or elsewhere on $PATH
;;;
;;; The go-rename-command variable can be customized to specify an
;;; alternative location for the installed command.
(defgroup go-rename nil
  "Options specific to the Go rename."
  :group 'go)

(defcustom go-rename-command "gorename"
  "The `gorename' command; by the default, $PATH is searched."
  :type 'string
  :group 'go-rename)

(defun go-rename (new-name)
  "Rename the entity denoted by the identifier at point, using
the `gorename' tool."
  (interactive (list (read-string "New name: " (thing-at-point 'symbol))))
  (if (not buffer-file-name)
      (error "Cannot use go-rename on a buffer without a file name"))
  ;; It's not sufficient to save the current buffer if modified,
  ;; since if gofmt-before-save is on the before-save-hook,
  ;; saving will disturb the selected region.
  (if (buffer-modified-p)
      (error "Please save the current buffer before invoking go-rename"))
  ;; Prompt-save all other modified Go buffers, since they might get written.
  (save-some-buffers nil #'(lambda ()
                             (and (buffer-file-name)
                                  (string= (file-name-extension (buffer-file-name)) ".go"))))
  (let* ((posflag (format "-offset=%s:#%d"
                          (expand-file-name buffer-file-truename)
                          (1- (position-bytes (point)))))
         (env-vars (go-root-and-paths))
         (goroot-env (concat "GOROOT=" (car env-vars)))
         (gopath-env (concat "GOPATH=" (mapconcat #'identity (cdr env-vars) ":")))
         success)
    (with-current-buffer (get-buffer-create "*go-rename*")
      (setq buffer-read-only nil)
      (erase-buffer)
      (let ((args (list go-rename-command nil t nil posflag "-to" new-name)))
        ;; Log the command to *Messages*, for debugging.
        (message "Command: %s:" args)
        (message "Running gorename...")
        ;; Use dynamic binding to modify/restore the environment
        (setq success (zerop (let ((process-environment (list* goroot-env gopath-env process-environment)))
                               (apply #'call-process args))))
        (insert "\n")
        (compilation-mode)
        (setq compilation-error-screen-columns nil)

        ;; On success, print the one-line result in the message bar,
        ;; and hide the *go-rename* buffer.
        (let ((w (display-buffer (current-buffer))))
          (if success
              (progn
                (message "%s" (go--buffer-string-no-trailing-space))
                (delete-window w))
            ;; failure
            (message "gorename exited")
            (shrink-window-if-larger-than-buffer w)
            (set-window-point w (point-min)))))))

  ;; Reload the modified files, saving line/col.
  ;; (Don't restore point since the text has changed.)
  ;;
  ;; TODO(adonovan): should we also do this for all other files
  ;; that were updated (the tool can print them)?
  (let ((line (line-number-at-pos))
        (col (current-column)))
    (revert-buffer t t t) ; safe, because we just saved it
    (goto-char (point-min))
    (forward-line (1- line))
    (forward-char col)))


(defun go--buffer-string-no-trailing-space ()
  (replace-regexp-in-string "[\t\n ]*\\'"
                            ""
                            (buffer-substring (point-min) (point-max))))

(provide 'go-rename)
